package com.yeziji.security.utils;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSONObject;
import com.yeziji.common.base.UserOnlineBase;
import com.yeziji.constant.SecuritySal;
import com.yeziji.security.common.SecurityToken;
import com.yeziji.security.msg.SecurityTokenErrorMsg;
import com.yeziji.utils.expansion.Asserts;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

/**
 * jwt 工具类
 *
 * @author hwy
 * @since 2023/11/12 14:38
 **/
public class JwtUtils {
    /**
     * 默认 JWT 有效期 (15 天)
     */
    public static final long DEFAULT_EXPIRED = 1000 * 24 * 60 * 60 * 15;

    /**
     * 默认追加的 JWT 有效期（7 天）
     */
    public static final long ADDITIONAL_EXPIRED = 1000 * 24 * 60 * 60 * 7;

    /**
     * 初始化 security token 对象
     *
     * @param userOnlineBase 初始化的 json 数据信息
     * @param expired        指定有效期
     * @return {@link SecurityToken} security 加密令牌
     */
    public static SecurityToken newSecurityToken(UserOnlineBase userOnlineBase, Long expired) {
        // 生成初始 token
        String originalToken = generateToken(userOnlineBase, expired = (expired == null ? DEFAULT_EXPIRED : expired));
        // 在初始 token 基础上获取追加时间后的 token
        long refreshExpiredTime = expired + ADDITIONAL_EXPIRED;
        return SecurityToken.builder()
                .originalToken(originalToken)
                .refreshToken(generateToken(userOnlineBase, refreshExpiredTime))
                .build();
    }

    /**
     * 根据初始令牌获取刷新令牌
     *
     * @param token       原始令牌
     * @param expiredTime 在 copy 之后追加令牌的有效时间
     * @return {@link String} 新的令牌
     */
    public static String copyTokenAndNewExpired(String token, long expiredTime) {
        Asserts.notBlank(token, SecurityTokenErrorMsg.ORIGINAL_TOKEN_IS_BLANK);

        Claims claims = getClaims(token);
        UserOnlineBase userOnlineBase = BeanUtil.toBean(claims, UserOnlineBase.class);
        return generateToken(userOnlineBase, claims.getExpiration().getTime() + expiredTime);
    }

    /**
     * 根据刷新令牌进行续期, 返回一个新的 securityToken 对象
     *
     * @param refreshToken 刷新令牌
     * @return {@link SecurityToken} 新的令牌对象
     */
    public static SecurityToken renewalTokenByRefreshToken(String refreshToken) {
        Asserts.notBlank(refreshToken, SecurityTokenErrorMsg.ORIGINAL_TOKEN_IS_BLANK);

        Claims claims = getClaims(refreshToken);
        UserOnlineBase userOnlineBase = BeanUtil.toBean(claims, UserOnlineBase.class);
        return newSecurityToken(userOnlineBase, DEFAULT_EXPIRED);
    }


    /**
     * 根据 token 获取用户在线信息
     *
     * @param token 加密令牌
     * @return {@link UserOnlineBase} 用户在线信息
     */
    public static UserOnlineBase getUserOnlineBaseByToken(String token) {
        return BeanUtil.toBean(getClaims(token), UserOnlineBase.class);
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 传入令牌 token
     * @return {@link Boolean} 是否已过期
     */
    public static boolean isExpired(String token) {
        try {
            return getClaims(token).getExpiration().before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 获取剩余时间
     *
     * @param token token 传入的令牌
     * @return {@link Long} 令牌剩余时间
     */
    public static long getRemainingTime(String token) {
        try {
            return getClaims(token).getExpiration().getTime() - System.currentTimeMillis();
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 生成 jwt token 令牌
     *
     * @param userOnlineBase 一般是指任意继承 UserOnlineBase 的子类
     * @param expired        过期时间
     * @return {@link String} token 令牌
     */
    private static String generateToken(UserOnlineBase userOnlineBase, long expired) {
        return Jwts.builder()
                // 加密 json 信息
                .setClaims(JSONObject.from(userOnlineBase))
                // 主题盐值
                .setSubject(SecuritySal.JwtSalt.SUBJECT_SALT)
                // 签发时间
                .setIssuedAt(new Date())
                // 有效时间
                .setExpiration(new Date(System.currentTimeMillis() + expired))
                // 签名盐值
                .signWith(SignatureAlgorithm.HS256, SecuritySal.JwtSalt.SIGN_SALT)
                .compact();
    }

    /**
     * 获取令牌的加密信息
     *
     * @param token 令牌
     * @return {@link Claims} 一些基本信息
     */
    private static Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(SecuritySal.JwtSalt.SIGN_SALT)
                .parseClaimsJws(token)
                .getBody();
    }
}
